import numpy as np#这个要有
import time
from game import Board
from policy_value_net import PolicyValueNet
from mcts_alphaZero import MCTSPlayer
import torch
#这一部分（到分割线前）需要自行修改
#设置棋盘大小
board_size = 9
#设置判断长度
juage_len = 5
#设置房间号
room_id = "19"
#加载自己的agent
# map = {}
# with open('value.txt', 'r') as f:
#     for line in f:
#         item = line.split(': ')
#         map[item[0]] = float(item[1])

#实现一个函数，接收一个状态，返回下一个棋子的位置
#用户只需要返回一个位置，黑白信息是程序自动管理的，
#程序管理的黑白信息只用于画图，不与用户函数共享
#所以用户要首先根据状态判断自己是先（黑）还是后（白）
#实际对战中没法保证是先手还是后手，先进入的是黑
#这里是3*3的agent示例
#提供当前状态state，格式：np.array、shape：(board_size**2)、0,1,2：空，黑，白
#提供历史信息history，格式[posi1,posi2,posi3,...]只用state可以不使用
def best_posi_of(state,history):
    print(state)
    # 落子
    move = mcts_player.get_action(board)
    posi = int(move)
    board.do_move(move)
    
    # #简单的判断方法，一样多该黑子下，否则白子
    # if np.sum(state==1)==np.sum(state==2):
    #     #黑子的逻辑，用户根据自己的agent改
    #     maxv = -100
    #     posi = -1
    #     for i in range(board_size ^ 2):
    #         if state[i]==0:
    #             new_state = state*1
    #             new_state[i] = 1
    #             if posi == -1:
    #                 posi = i
    #             if str(new_state) in map and map[str(new_state)] > maxv:
    #                 maxv = map[str(new_state)]
    #                 posi = i
    # #白子的逻辑，同样，需要自己修改
    # else:
    #     minv = 100
    #     posi = -1
    #     for i in range(board_size ^ 2):
    #         if state[i]==0:
    #             new_state = state*1
    #             new_state[i] = 2
    #             if posi == -1:
    #                 posi = i
    #             if str(new_state) in map and map[str(new_state)] < minv:
    #                 minv = map[str(new_state)]
    #                 posi = i
    #最后一定要返回一个整数作为落点
    return posi

###################分割线#####################之后的部分不需要修改###################
history = []
state = np.array([0]*board_size**2)
def update_state(state,posi,step,is_second):
    global history
    state[posi] = (step)%2 + 1
    history+=[posi]
    return state

#必要的库
import tkinter as tk
import socketio
#控制结束，避免多下
is_finish = False
#这是第几个子，用于控制颜色
step = 1
#是否允许落子，用于避免在完成匹配之前落子或者连下多字
start_flag = False
#简单的手动操作界面
class Gobang:
    def __init__(self, sio):
        #sio用于通信，可以告知对手自己的落点
        self.window = tk.Tk()
        self.window.title("五子棋")
        self.canvas = tk.Canvas(self.window, width=60+board_size*40, height=60+board_size*40, bg="tan")
        self.canvas.pack()
        self.draw_board()
        self.canvas.bind("<Button-1>", self.click_handler)
        self.board = [[0] * board_size for _ in range(board_size)]
        self.sio = sio

    #画棋盘
    def draw_board(self):
        for i in range(board_size):
            self.canvas.create_line(50, 50 + i * 40, 10+board_size * 40, 50 + i * 40)
            self.canvas.create_line(50 + i * 40, 50, 50 + i * 40, 10+board_size * 40)

    #点击事件处理
    def click_handler(self, event):
        #禁止手动落子
        return
        #首先判断是否允许落子
        global start_flag
        print(start_flag)
        if start_flag == True:
            #确保不可以连下
            start_flag = False
            #计算落点
            row = round((event.y - 50) / 40)
            column = round((event.x - 50) / 40)
            #不考虑棋盘之外的点击
            if column < 0 or column > board_size-1:
                return
            if row < 0 or row > board_size-1:
                return
            #两个子不能落在一个位置
            if self.board[column][row] == 0:
                #将落点转化成整数
                position = row*board_size+column
                #通知对手本次的落点
                self.sio.emit('drop', position)
                #在自己的棋盘上显示
                self.drop(position)
            else:
                #如果操作失误，允许重新落子
                start_flag = True
                    
    #在棋盘上画棋子
    def draw_piece(self, row, column, color):
        if color == "black":
            self.canvas.create_oval(30 + column * 40, 30 + row * 40, 70 + column * 40, 70 + row * 40, fill="black")
        else:
            self.canvas.create_oval(30 + column * 40, 30 + row * 40, 70 + column * 40, 70 + row * 40, fill="white")

    #简单的胜负判断
    def is_win(self, row, column, player):
        directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
        for d_row, d_column in directions:
            count = 1
            for i in range(1, board_size):
                if 0 <= row + i * d_row < board_size and 0 <= column + i * d_column < board_size and \
                self.board[column + i * d_column][row + i * d_row] == player:
                    count += 1
                else:
                    break
            for i in range(1, board_size):
                if 0 <= row - i * d_row < board_size and 0 <= column - i * d_column < board_size and \
                self.board[column - i * d_column][row - i * d_row] == player:
                    count += 1
                else:
                    break
            if count >= juage_len:
                return True
        return False

    #根据整数在棋盘上显示棋子
    def drop(self,posi):
        global is_finish
        global step
        #恢复坐标
        column = posi % board_size
        row = posi // board_size
        #判断颜色并绘制
        if step % 2 == 1:
            self.draw_piece(row,column, "black")
            self.board[column][row] = 1
            if self.is_win(row,column, 1):
                is_finish = True
                self.show_result("black")
        else:
            self.draw_piece(row,column, "white")
            self.board[column][row] = 2
            if self.is_win(row,column, 2):
                is_finish = True
                self.show_result("white")
        #更新下一个子的序号
        step += 1

    #显示胜利者的信息
    def show_result(self, winner):
        self.canvas.unbind("<Button-1>")
        self.canvas.create_text(90, 65, text=f"{winner} wins!", font=("Helvetica", 20, "bold"), fill="red", anchor="center", tags="result")

    def run(self):
        self.window.mainloop()

    def close(self):
        try:
            self.window.destroy()
        except:
            pass


# 创建一个 SocketIO 客户端实例
sio = None
sio = socketio.Client()
game = None
game = Gobang(sio)
# 当连接到服务器时调用
@sio.event
def connect():
    print("Connected to the server.\nWaiting...")
# 当断开连接时调用
@sio.event
def disconnect():
    print("Disconnected from server.")
    
#房间已满
@sio.event
def full():
    print("full.")
    game.close()

#默认后手
is_second = True
# 先手通知
@sio.event
def first():
    #先手的一方会解锁，这时允许落子，为了通用不要求在中心
    global state
    global start_flag
    global is_second
    is_second = False
    start_flag = True
    print("You are first.")
    position = best_posi_of(state,history)
    #通知对手本次的落点
    sio.emit('drop', position)
    #在自己的棋盘上显示
    game.drop(position)
    state = update_state(state,position,step,is_second)
    # 修改：设置先手
    mcts_player.set_player_ind(1)

# 接受对方的落点并下棋
@sio.event
def drop(posi):
    #对方落子后，本地同步更新
    print("drop")
    global step
    global is_finish
    global start_flag
    global state
    global is_second
    time.sleep(3)
    start_flag = True
    game.drop(posi)
    start_flag = True
    state = update_state(state,posi,step,is_second)
    # 将对手的落子放入棋盘中
    board.do_move(posi)
    #计算落点
    if np.sum(state==0) > 0:
        if is_finish == False:
            position = best_posi_of(state,history)
            #通知对手本次的落点
            sio.emit('drop', position)
            print(position)
            #在自己的棋盘上显示
            game.drop(position)
            state = update_state(state,position,step,is_second)
@sio.event
def quit():
    #对手退出，自动断开连接
    print("rival quit.")
    game.close()

# 连接到服务器
sio.connect('http://39.106.205.217:3333')
# 可以使用自己的学号作为room_id,避免测试时胡乱匹配
sio.emit('room', room_id)
# 修改：初始化棋盘和MCT，装载策略网络参数，默认设置后手
model_file = 'best_policy_new.model'
torch.cuda._initialized = True
board = Board(width=board_size, height=board_size, n_in_row=juage_len)
best_policy = PolicyValueNet(board_size, board_size, model_file = model_file,  use_gpu = True)
mcts_player = MCTSPlayer(best_policy.policy_value_fn, c_puct=5, n_playout=400)
board.init_board(0)
mcts_player.set_player_ind(2)

game.run()
sio.disconnect()
